#!/bin/sh
# restart using tclsh \
exec tclsh "$0" "$@"
# Next line is the first line seen by tclsh
## -*-Tcl-*-
 # ###################################################################
 # 
 #  File: "alphac"
 #                                    created: 2003-09-10 14:03:48 
 #                                last update: 2017-03-27 09:35:35
 #  
 # Author: Frédéric Boulanger
 # Contributors: Bernard Desgraupes
 # Copyright (c) 2003-2017 Frédéric Boulanger and contributors
 # All rights reserved.
 #  
 #  Description: 
 #  
 #  This file is a Tcl script that can be used on Mac OS X to make Alpha
 #  edit files. It relies on the Alpha Server package to communicate with
 #  Alpha (this is an always-on package which is automatically activated by
 #  Alpha at startup). If it cannot connect to the Alpha Server, it will
 #  attempt to use /usr/bin/osascript instead to communicate with Alpha
 #  through AppleScript.
 # 
 # This script should be put in Alpha's $HOME/Tools/ directory so that it
 # can be used by alphaServer as the TEXEDIT program: alphac may be used to
 # edit TeX files by setting TEXEDIT to:
 #    alphac +%d %s
 # You can also copy it to any location on your path in order to use it
 # from a Terminal window.
 #  
 #  The usage of alphac is:
 #  
 #    alphac ?options? ?linespec? -- file [file+]
 #    alphac ?options? ?linespec? -c name [string|-]
 #      
 # The available options are:
 #     -a        use AppleScript (bypass the AlphaServer)
 #     -c        create new window
 #     -d        print debug information
 #     -e enc    encoding
 #     -h        print help
 #     -m str    mode
 #     -p num    port number
 #     -u str    user name
 #     -v        verbose
 #     -w        wait
 # The linespec argument lets you specify a region to hilite. The format is: 
 #      +line1[.col1][,line2[.col2]]
 # If ".col1" is omitted, the selection will start at the beginning of line1.
 # If ".col2" is omitted, the selection will stop after the end of line2.
 # The linespec argument applies to the last opened file.
 #      
 # The -c argument asks Alpha to open a new window. Optionally, the name of
 # the window may be followed by text or by a dash to read initial contents
 # from standard input:
 #        alphac -c <window_name> [<string>+]
 #        alphac -c <window_name> [-]
 #      
 #  In less compact form:
 #  
 #    - To edit a file or a list of files, use:
 #        alphac file other_file ...
 #  
 #    - To edit a file at a given line, use:
 #        alphac +line file
 #  
 #    - To edit a file and have a range of lines selected, use:
 #        alphac +line1,line2 file
 #  
 #    - To make the selection start at a given column on line1 and stop at 
 #      a given column on line2, use:
 #        alphac +line1.col1,line2.col2 file
 #  
 #    - To create a new window in Alpha, use:
 #        alphac -c name
 #  
 #      The initial contents of the window can be the concatenation of strings:
 #        alphac -c name string1 string2 ...
 #      or it can be read from standard input with:
 #        alphac -c name -
 #  
 #    - If you want to edit a file in TeX mode:
 #        alphac -m TeX file
 #  
 #    - If you want to edit a file in macRoman encoding:
 #        alphac -e macRoman file
 #  
 #    - If you want alphac to return only when all the windows you create/edit
 #      have been closed, use:
 #        alphac -wait
 #  
 # ###################################################################
 ##

namespace eval alfac {
	variable defaultPort 51954
}

global env

set progName [file tail $argv0]
set versionNumber "5.0"

# ======================================================================
# Part 1: define procs [serverPortFileName] and [sendToAlphaServer]
# ======================================================================

# Figure out where the file containing the port number is
proc serverPortFileName {userName} {
    # Find a place for the file which contains the port number of 
    # the server so that other processes can know it.
    switch -- $::tcl_platform(platform) {
	"macintosh" {
	    return ""
	}
	"unix" {
	    set tmpdir "/tmp"
	}
	"windows" {
	    if {[info exists ::env(TEMP)] && [file isdirectory $::env(TEMP)]} {
		set tmpdir $::env(TEMP)
	    } elseif {[info exists ::env(TMP)] && [file isdirectory $::env(TMP)]} {
		set tmpdir $::env(TMP)
	    } elseif {[info exists ::env(USERPROFILE)] \
	      && [file isdirectory [file join $::env(USERPROFILE) Temp]]} {
		set tmpdir [file join $::env(USERPROFILE) Temp]
	    } else {
		return ""
	    }
	}
	"default" {
	    return ""
	}
    }
    return [file join $tmpdir ${userName}-AlphaServerPort]
}

## 
 # ------------------------------------------------------------------------
 # 
 # "sendToAlphaServer" --
 # 
 # General proc for sending a command to AlphaServer
 # 
 # For security reasons, the AlphaServer executes commands in a safe
 # interpreter, and will only accept the commands listed in the variable
 # alphaServer::authorizedCommands. 
 # 
 # ------------------------------------------------------------------------
 ##
proc sendToAlphaServer {command {wait 0} {winContents ""}} {
    global env
    global userName
    global progName
	global useOSA
	global portNumber

	if {!$useOSA} {
		if {$portNumber > 0} {
			set env(ALPHASERVERPORT) $portNumber
		}
		# The port number of the Alpha server is in ALPHASERVERPORT
		if { ![info exists env(ALPHASERVERPORT)] } {
			# If not found, try to read it from /tmp/$USER-AlphaServerPort
			set port [alfac::getPort]
			if { [string is integer -strict $port] } {
				set env(ALPHASERVERPORT) $port
			}
		}
		
		# First we try to use the AlphaServer.  This should work in most cases.
		if { [info exists env(ALPHASERVERPORT)] } {
			alfac::log "Connecting to Alpha on port $env(ALPHASERVERPORT)."
			alfac::logDebug "Alpha server port: $env(ALPHASERVERPORT)."
			# Open a connection to the server
			if {![catch {set sock [socket localhost $env(ALPHASERVERPORT)]}]} {
				alfac::logDebug "socket: $sock"
				# In this case the AlphaServer communication is working.  If
				# we are going to wait, we need to tell the Alpha Server
				# using the magic flag "-wait" (first entry in the list of
				# commands):
				if { $wait } {
					set command [linsert $command 0 "-wait"]
				}
				# Write the Tcl command
				puts -nonewline $sock $command
				# Write data between \02 (START OF TEXT) and \03 (END OF TEXT)
				# control codes
				if {$winContents ne ""} {
					alfac::logDebug "sending \\02 data \\03"
					puts -nonewline $sock "\02"
					puts -nonewline $sock $winContents
					puts -nonewline $sock "\03"	
				} 
				# Write an END OF TRANSMISSION control code to end the command
				alfac::logDebug "sending EOT"
				puts -nonewline $sock "\04"
				# Make sure everything has been sent
				flush $sock
				if { $wait } {
					# If we must wait for the end of the editing session, make a 
					# blocking read from the socket.
					alfac::logDebug "blocking the socket"
					fconfigure $sock -blocking 1
					puts stdout [read -nonewline $sock]
				}
				alfac::logDebug "closing the socket"
				close $sock
				return 0 ; # this is the exitcode
			}
		} 
	} 
    
    # At this point the communication with the Alpha Server has failed or
    # has been skipped. We are going to try with AppleScript, but in this
    # case the -wait flag will not work. Rather than fooling the caller or
    # just issuing a warning, let us be honest and clear:
    if {$wait} {
		alfac::error "cannot find Alpha server port (osascript alternative does not work with the -wait flag)."
    }

	# If no server can be found, try to use AppleScript with /usr/bin/osascript.
	if { [file executable /usr/bin/osascript] } {
		if {!$useOSA} {
			alfac::log "$progName warning: cannot find Alpha server port, using AppleScript instead."
		} 
		if {$winContents ne ""} {
			lappend command $winContents
		} 
		set as "
			tell application \"Alpha\"
			activate
			DoScript \"$command\"
			end tell
		"
		
		# Debug stuff
		alfac::logDebug "/usr/bin/osascript -l AppleScript -e $as"

		alfac::log "Sendind commands to Alpha through AppleScript."
		if {![catch {exec /usr/bin/osascript -l AppleScript -e $as} errMsg]} {
			return 0 ; # exitcode
		} 
		puts $errMsg
	}
    
    # None of the above worked...
    alfac::error "cannot find Alpha server port nor osascript."
    return 1 ; # exitcode
}


proc alfac::usage {} {
	global progName
    puts "Usage:"
    puts "    $progName ?options? ?linespec? ?--? file \[file+\]"
    puts "    $progName ?options? ?linespec? -c ?--? name \[string|-\]"
    puts "Options:"
    puts "    -a        use AppleScript"
    puts "    -c        create a new window"
    puts "    -d        print debugging information"
    puts "    -e enc    encoding"
    puts "    -h        print help"
    puts "    -m str    mode"
    puts "    -p num    port number"
    puts "    -s        print Alpha Server's port"
    puts "    -u str    user name"
    puts "    -v        verbose"
    puts "    -V        print version"
    puts "    -w        wait"
    puts "    --        end of options"
    puts "Linespec format: +line1\[.col1\]\[,line2\[.col2\]\]"
    puts -nonewline "Examples:"
    puts "
    $progName file1 file2
    $progName +2.4-3.9 file
    $progName -e macRoman -m TeX -- file
    $progName -c untitled text
    $progName -c untitled -
    $progName -c untitled - < file
"
    return
} 


proc alfac::log {msg} {
	global verbose
	if {$verbose} {
		puts $msg
	} 
	return
}


proc alfac::logDebug {msg} {
	global debugging
	if {$debugging} {
		puts stderr $msg
	} 
	return
}


proc alfac::error {msg} {
	global progName
	puts stderr "$progName error: $msg"
	exit 1
}


proc alfac::getPort {} {
    global userName
	variable defaultPort
	set port ""
	set alphaServerFile [serverPortFileName $userName]
	alfac::logDebug "looking for port in $alphaServerFile"
	if { [file readable $alphaServerFile] } {
		set fid [open $alphaServerFile r]
		gets $fid port
		close $fid
		set port [string trim $port]
	} else {
		alfac::logDebug "cannot read file $alphaServerFile"
		set port $defaultPort
	}
	return $port
}



# ======================================================================
# Part 2: The script itself, parsing arguments, and building commands 
# ======================================================================

# Initialization
# --------------

if {$argc == 0} {
	alfac::usage
    exit 1
}

if {[info exists ::env(USER)]} {
    set userName $::env(USER)
} elseif {[info exists ::env(USERNAME)]} {
    set userName $::env(USERNAME)
} elseif {[info exists ::tcl_platform(user)]} {
    set userName $::tcl_platform(user)
} else {
    set userName ""
}

set debugging 0
set encoding [encoding system]
set mode ""
set newWindow 0
set portNumber -1
set selection [list]
set syncEditing 0
set useOSA 0
set verbose 0

# Arguments parsing
# -----------------
alfac::logDebug "parsing the options"
set i 0
set stop 0
while {([set arg [lindex $argv $i]] ne "") && !$stop} {
	switch -glob -- $arg {
		"-a" {
			set useOSA 1
			set argv [lreplace $argv $i $i]
		}
		"-c" {
			set newWindow 1
			set argv [lreplace $argv $i $i]
		}
		"-d" {
			set debugging 1
			set argv [lreplace $argv $i $i]
		}
		"-e" {
			set encoding [lindex $argv [incr i]]
			set argv [lreplace $argv [expr {$i-1}] $i]
			incr i -1
		}
		"-h" - "-help" {
			set year [clock format [clock seconds] -format "%Y"]
			puts "$progName - version $versionNumber" 
			puts "Copyright 2003-$year, Frédéric Boulanger and Alpha Community"
			puts "Edit files in Alpha."
			alfac::usage
			exit 0
		}
		"-m" {
			set mode [lindex $argv [incr i]]
			set argv [lreplace $argv [expr {$i-1}] $i]
			incr i -1
		}
		"-p" {
			set portNumber [lindex $argv [incr i]]
			if {![string is integer $portNumber]} {
				alfac::error "port number must be an integer."
			} 
			set argv [lreplace $argv [expr {$i-1}] $i]
			incr i -1
		}
		"-s" {
			set port [alfac::getPort]
			if {$port ne ""} {
				puts $port
				exit 0
			} else {
				puts "can't find Alpha Server's port"
				exit 1
			} 
		}
		"-u" {
			set userName [lindex $argv [incr i]]
			set argv [lreplace $argv [expr {$i-1}] $i]
			incr i -1
		}
		"-v" {
			set verbose 1
			set argv [lreplace $argv $i $i]
		}
		"-V" {
			puts $versionNumber
			exit 0
		}
		"-w" - "-wait" {
			set syncEditing 1
			set argv [lreplace $argv $i $i]
		}
		"--" {
			set argv [lreplace $argv $i $i]
			set stop 1
			break
		}
		"+*" {
			set ok [regexp -- {\+(\d+)(?:\.(\d+))?(?:[-,:](\d+)(?:\.(\d+))?)?} $arg \
	  			-> line1 col1 line2 col2]
			if {$ok} {
				if {$arg eq "+$line1"} {
					set line2 $line1
				} 
				if {$col1 eq ""} {
					set col1 0
				} 
				if {$line2 eq ""} {
					set line2 $line1
					set col2 $col1
				} elseif {$line2 < $line1} {
					set l $line1
					set line1 $line2
					set line2 $l
				}
				if {$col2 eq ""} {
					incr line2
					set col2 0
				} 
				foreach p [list $col1 $line2 $col2] {
					if {![string is integer $p]} {
						set ok 0
					}
				} 
			} 
			if {!$ok} {
				alfac::error "$arg is not a valid selection spec. Format is:\n\t+line\[.col\]\[,line\[.col\]\]"
			}
			set selection "$line1-$col1-$line2-$col2"
			set argv [lreplace $argv $i $i]
		}
		default {
			set stop 1
			break
		}
	}
}

alfac::logDebug "  debugging: $debugging"
alfac::logDebug "   encoding: $encoding"
alfac::logDebug "       mode: $mode"
alfac::logDebug "  newWindow: $newWindow"
alfac::logDebug "  selection: $selection"
alfac::logDebug "syncEditing: $syncEditing"
alfac::logDebug "     useOSA: $useOSA"
alfac::logDebug "    verbose: $verbose"

if {$useOSA && $syncEditing} {
	alfac::error "flags -a and -w are incompatible."
} 
alfac::logDebug "remaining args: $argv"

set argc [llength $argv]
if {$argc == 0} {
	alfac::error "not enough arguments. See '$progName -h'."
} 
set idx 0


# There are two cases: 
#     * the first one is with the -c flag. We open a new window, not
#     associated to a file on disk. This is handled by the proc
#     [alphaServer::newWindow].
# 
#     * in the second case, there can be any number of files to edit. If a
#     linespec is specified, it applies to the last file. This is handled
#     by the proc [alphaServer::editFiles].

alfac::logDebug "building the command"
set winContents ""
if {$newWindow} {
    # Case 1: new window
	set winName [lindex $argv $idx]  
	alfac::logDebug "winName: $winName"
	incr idx
	if {$idx < $argc} {
		set arg [lindex $argv $idx]
		if {$arg == "-"} {
			set lineslist [list]
			while { ![eof stdin] } {
				gets stdin line
				lappend lineslist $line
			}
			set winContents [join $lineslist \n]
		} else {
			set winContents [join [lrange $argv 1 end]]
		}
	}
    set command [list newWindow $winName $mode $encoding $selection]
} else {
    # Case 2: any number of files, but without linespecs
    set command [list editFiles $mode $encoding $selection]
	set arg [lindex $argv $idx]
	while 1 {
		set f [file normalize $arg]
		if {![file readable $f]} {
			alfac::error "file \"$arg\" is not readable"
			exit 1
		} else {
			lappend command $f
		}
		incr idx
		if {$argc <= $idx} {
			break
		}
		set arg [lindex $argv $idx]
	}
}

# Now send the command
alfac::logDebug "contacting Alpha server
  command: $command
     wait: $syncEditing"
set exitcode [sendToAlphaServer $command $syncEditing $winContents]
exit $exitcode


# ###################################################################
#  History
# 
#  modified   by  rev reason
#  ---------- --- --- -----------
#  2003-09-10 FBO 1.0 original
#  2003-10-15 FBO 2.0 accept parameters a la AlphaTk, use basic Alpha commands
#  2003-11-17 FBO 3.0 allow '-wait' to make alphac wait for the edited/created
#                     windows to be closed (suggested by J. Guyer).
#  2005-10-19  JK 4.0 Internal changes -- no changes in the command line.  
#                     Send instructions as single commands (a list) instead of 
#                     sending long scripts.  Use "-wait" as first entry in the 
#                     list to instruct Alpha Server to keep the channel open 
#                     until all involved window are closed.
#  2017-03-22  BD 5.0 Updated for AlphaCocoa. Added options -u,-p,-a,-e,-m.

